package com.study.springboot.common.util;

import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSON;
import com.study.springboot.common.constant.ErrorRespStatusEnum;
import com.study.springboot.common.mvc.CustomedException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author wei.zhao
 * @description: redis工具类
 * @date 2021年12月06日 15:26
 */
@Slf4j
@Component
public class RedisUtil {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 默认过期时间
     */
    private static final int DEFAULT_EXPIRE = 30;

    /**
     * 默认过期时间单位
     */
    private static final TimeUnit DEFAULT_EXPIRE_UNIT = TimeUnit.SECONDS;

    /**
     * 默认最大重试次数
     */
    private static final int DEFAULT_RETRY = 3;

    /**
     * 默认等待时间
     */
    private static final int DEFAULT_WAIT = 1;

    /**
     * 默认等待时间单位
     */
    private static final TimeUnit DEFAULT_WAIT_UNIT = TimeUnit.SECONDS;

    /**
     * 获取值
     */
    public String getKey(String key) {
        return stringRedisTemplate.boundValueOps(key).get();
    }

    /**
     * 判断key是否存在
     */
    public Boolean hasKey(String key) {
        return stringRedisTemplate.hasKey(key);
    }

    /**
     * 删除某个key
     */
    public void deleteKey(String key) {
        stringRedisTemplate.delete(key);
    }

    /**
     * 设置一个值
     */
    public void setKey(String key, String value) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        ops.set(key, value);
    }

    /**
     * 设置一个值并设置有效期
     */
    public void setKey(String key, String value, long timeout, TimeUnit unit) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        Assert.isTrue(null != unit, "TimeUnit is not null");
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        ops.set(key, value, timeout, unit);
    }

    /**
     * 获取对象
     */
    public <T> T getObject(String key, Class<T> clazz) {
        String value = this.getKey(key);
        return JsonUtil.toBean(value, clazz);
    }

    /**
     * 获取List
     */
    public <T> List<T> getList(String key, Class<T> clazz) {
        String value = this.getKey(key);
        return JsonUtil.toList(key, clazz);
    }

    /**
     * 设置对象
     */
    public void setObject(final String key, Object object) {
        String value = JsonUtil.toJSON(object);
        this.setKey(key, value);
    }

    /**
     * 设置对象（含过期时间）
     */
    public void setObject(final String key, Object object, long timeout, TimeUnit unit) {
        String value = JsonUtil.toJSON(object);
        this.setKey(key, value, timeout, unit);
    }

    /**
     * 设置List
     */
    public <T> void setList(String key, List<T> object) {
        String value = JsonUtil.toJSON(object);
        this.setKey(key, value);
    }

    /**
     * 设置List（含过期时间）
     */
    public <T> void setList(String key, List<T> object, long timeout, TimeUnit unit) {
        String value = JsonUtil.toJSON(object);
        this.setKey(key, value, timeout, unit);
    }


    /**
     * 设置过期时间（如等过了10分钟过期）
     */
    public void expire(String key, long timeout, TimeUnit unit) {
        stringRedisTemplate.expire(key, timeout, unit);
    }

    /**
     * 设置过期时间（如在凌晨0点过期）
     */
    public void expireAt(String key, long date) {
        stringRedisTemplate.expireAt(key, new Date(date));
    }

    /**
     * 添加元素至ZSet
     */
    public void zadd(String key, String value, Long score) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        ops.add(key, value, score);
    }


    /**
     * 获取指定score范围的ZSet
     */
    public Set<String> zrangeByScore(String key, Long min, Long max) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        Set<String> sets = ops.rangeByScore(key, min, max);
        return sets;
    }

    /**
     * 获取指定索引范围的ZSet（set中仅含元素）
     */
    public Set<String> zrange(String key, Long start, Long end) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        Set<String> sets = ops.range(key, start, end);
        return sets;
    }

    /**
     * 获取指定索引范围的ZSet（set中仅含元素）
     */
    public <T> Set<T> zrange(String key, Long start, Long end, Class<T> classz) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        Set<String> set = ops.range(key, start, end);
        Set<T> concurrentHashSet = new ConcurrentHashSet();
        set.forEach(s -> {
            concurrentHashSet.add(JsonUtil.toBean(s, classz));
        });
        return concurrentHashSet;
    }

    /**
     * 获取指定索引范围的Set（set中含元素和score）
     */
    public Set<ZSetOperations.TypedTuple<String>> zrangeWithScores(String key, Long start, Long end) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<String>> sets = ops.rangeWithScores(key, start, end);
        return sets;
    }

    /**
     * 获取指定索引范围的Set（set中仅含元素）（索引从大到小排序）
     */
    public Set<String> zrevrange(String key, Long start, Long end) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        Set<String> sets = ops.reverseRange(key, start, end);
        return sets;
    }

    /**
     * 获取指定索引范围的Set（set中含元素和score）（索引从大到小排序）
     */
    public Set<ZSetOperations.TypedTuple<String>> zrevrangeWithScores(String key, Long start, Long end) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<String>> sets = ops.reverseRangeWithScores(key, start, end);
        return sets;
    }

    /**
     * 修改ZSet指定元素的score值
     */
    public Double zIncrby(String key, String value, Double delta) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        return ops.incrementScore(key, value, delta);
    }

    /**
     * 移除ZSet某个元素
     */
    public void zremove(String key, Object... objects) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        ops.remove(key, objects);
    }

    /**
     * 返回ZSet某个元素的score
     */
    public Double zscore(String key, String value) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        Double score = ops.score(key, value);
        return score;
    }

    /**
     * 自增（设置超时时间）
     */
    public Long incr(String key, long liveTime) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        Long increment = entityIdCounter.getAndIncrement();
        //初始设置过期时间
        boolean b = (null == increment || increment.longValue() == 0) && liveTime > 0;
        if (b) {
            entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
        }
        return increment;
    }

    /**
     * 自增（设置超时时间）
     */
    public Long incr(String key, long liveTime, TimeUnit unit) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        Long increment = entityIdCounter.getAndIncrement();
        //初始设置过期时间
        boolean b = (null == increment || increment.longValue() == 0) && liveTime > 0;
        if (b) {
            entityIdCounter.expire(liveTime, unit);
        }
        return increment;
    }

    /**
     * 自增（设置超时时间）
     */
    public Long incrResetLive(String key, long liveTime) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        Long increment = entityIdCounter.getAndIncrement();
        //重置过期时间
        entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
        return increment;
    }

    /**
     * 自增1
     */
    public long increment(String key) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        Long increment = entityIdCounter.getAndIncrement();
        return increment;
    }

    /**
     * 自减1
     */
    public long decrement(String key) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        Long decrement = entityIdCounter.getAndDecrement();
        return decrement;
    }

    /**
     * 自增指定值
     */
    public Long increment(final String key, final long value) {
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        return ops.increment(key, value);
    }

    public boolean lock(String key) {
        return lock(key, DEFAULT_EXPIRE, DEFAULT_EXPIRE_UNIT);
    }

    public boolean lock(String key, int expire, TimeUnit expireUnit) {
        return lock(key, expire, expireUnit, DEFAULT_RETRY, DEFAULT_WAIT, DEFAULT_WAIT_UNIT);
    }

    public boolean lock(String key, int retry) {
        return lock(key, retry, DEFAULT_WAIT, DEFAULT_WAIT_UNIT);
    }

    public boolean lock(String key, int retry, int wait, TimeUnit waitUnit) {
        return lock(key, DEFAULT_EXPIRE, DEFAULT_EXPIRE_UNIT, retry, wait, waitUnit);
    }

    public boolean lock(String key, int expire, TimeUnit expireUnit, int retry, int wait, TimeUnit waiUnit) {
        for (; retry > 0; retry--) {
            long ts = System.currentTimeMillis();
            String val = ts + TimeoutUtils.toMillis(expire, expireUnit) + "";
            if (stringRedisTemplate.opsForValue().setIfAbsent(key, val, expire, expireUnit)) {
                return true;
            }
            // [针对死锁]当前时间大于超时时间，则释放原锁并获取锁
            synchronized (key) {
                String sts = stringRedisTemplate.opsForValue().get(key);
                if (StringUtils.isNotBlank(sts) && ts > Long.valueOf(sts)) {
                    stringRedisTemplate.opsForValue().set(key, val, expire, expireUnit);
                    return true;
                }
            }
            try {
                waiUnit.sleep(wait);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        throw new CustomedException(ErrorRespStatusEnum.OBTAIN_DISTRIBUTED_LOCK_FAILED);
    }

    /**
     * 尝试上锁
     * 注：若上锁成功则返回true，否则返回false
     */
    public Boolean tryLock(String key) {
        return tryLock(key, DEFAULT_EXPIRE, DEFAULT_EXPIRE_UNIT);
    }

    /**
     * 尝试上锁
     * 注：若上锁成功则返回true，否则返回false
     */
    public Boolean tryLock(String key, int expire, TimeUnit expireUnit) {
        long ts = System.currentTimeMillis();
        String val = ts + TimeoutUtils.toMillis(expire, expireUnit) + "";
        if (stringRedisTemplate.opsForValue().setIfAbsent(key, val, expire, expireUnit)) {
            return true;
        }
        return false;
    }

    /**
     * 解锁操作
     */
    public void unlock(String lockKey) {
        stringRedisTemplate.delete(lockKey);
    }

    /**
     * 从队列头开始放入元素
     */
    public long lpush(String key, String value) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        long len = stringRedisTemplate.boundListOps(key).leftPush(value);
        return len;
    }

    /**
     * 从队列头开始放入元素
     */
    public long lpush(String key, Object object) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        String value = JSON.toJSONString(object);
        long len = stringRedisTemplate.boundListOps(key).leftPush(value);
        log.debug("lpush. [OK] key={}, object={}", key, value);
        return len;
    }

    /**
     * 从队列头开始放入元素
     */
    public void lpush(String key, String value, Long time, TimeUnit timeUnit) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                stringRedisTemplate.boundListOps(key).leftPush(value);
                stringRedisTemplate.boundListOps(key).expire(time, timeUnit);
                return operations.exec();
            }
        });
        log.debug("lpush. [OK] key={}, value={}", key, value);
    }

    /**
     * 从队列头开始放入元素
     */
    public void lpush(String key, List<String> value, Long time, TimeUnit timeUnit) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                stringRedisTemplate.boundListOps(key).leftPushAll(value.toArray(new String[]{}));
                stringRedisTemplate.boundListOps(key).expire(time, timeUnit);
                return operations.exec();
            }
        });
        log.debug("lpush. [OK] key={}, value={}", key, value.stream().toArray());
    }

    /**
     * 从队列尾开始放入元素
     */
    public long rpush(String key, String value) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        long len = stringRedisTemplate.boundListOps(key).rightPush(value);
        log.debug("lpush. [OK] key={}, value={}", key, value);
        return len;
    }

    /**
     * 从队列尾开始放入元素
     */
    public long rpush(String key, Object object) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        String value = JSON.toJSONString(object);
        long len = stringRedisTemplate.boundListOps(key).rightPush(value);
        log.debug("lpush. [OK] key={}, value={}", key, value);
        return len;
    }

    /**
     * 从队列尾移除元素并返回这个元素
     */
    public String rpop(String key) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        String value = stringRedisTemplate.boundListOps(key).rightPop();
        log.debug("rpop. [OK] key={}, value={}", key, value);
        return value;
    }

    /**
     * 从队列尾移除元素并返回这个元素
     */
    public <T> T rpop(String key, Class<T> clazz) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        String value = stringRedisTemplate.boundListOps(key).rightPop();
        log.debug("rpop. [OK] key={}, value={}", key, value);
        return JSON.parseObject(value, clazz);
    }

    /**
     * 从队列尾移除元素并返回这个元素（没有元素会阻塞直至等待元素出现或者超时）
     */
    public String brpop(String key, long timeout, TimeUnit unit) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        String value = stringRedisTemplate.boundListOps(key).rightPop(timeout, unit);
        log.debug("brpop. [OK] key={}, value={}", key, value);
        return value;
    }

    /**
     * 从队列尾移除元素并返回这个元素（没有元素会阻塞直至等待元素出现或者超时）
     */
    public <T> T brpop(String key, Class<T> clazz, long timeout, TimeUnit unit) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        String value = stringRedisTemplate.boundListOps(key).rightPop(timeout, unit);
        log.debug("brpop. [OK] key={}, value={}", key, value);
        return JSON.parseObject(value, clazz);
    }

    /**
     * 从队列中删除某个元素（字符串）
     */
    public Long lremove(String key, String value) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        Long result = stringRedisTemplate.boundListOps(key).remove(0, value);
        log.debug("lrem. [OK] key={}, value={}", key, value);
        return result;
    }

    /**
     * 从队列中删除某个元素（对象）
     */
    public Long lremove(String key, Object object) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        String value = JSON.toJSONString(object);
        Long result = stringRedisTemplate.boundListOps(key).remove(0, value);
        log.debug("lrem. [OK] key={}, value={}", key, value);
        return result;
    }

    /**
     * 查找队列中某个范围的元素
     */
    public List<String> lrange(String key, long start, long end) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        List<String> list = stringRedisTemplate.boundListOps(key).range(start, end);
        log.debug("lrange. [OK] key={}, start={}, end={}", key, start, end);
        return list;
    }

    /**
     * 返回队列长度
     */
    public long llen(String key) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        long len = stringRedisTemplate.boundListOps(key).size();
        log.debug("llen. [OK] key={}", key);
        return len;
    }

    /**
     * 按照索引位置插入元素
     * 若索引位置存在元素，则把当前元素插在索引元素后面，如果不存在则插在最后面
     */
    public long lByIndex(String key, Long index, String value) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        String v = stringRedisTemplate.boundListOps(key).index(index);
        if (!StringUtils.isEmpty(v)) {
            return stringRedisTemplate.boundListOps(key).rightPush(v, value);
        } else {
            return stringRedisTemplate.boundListOps(key).leftPush(value);
        }
    }

    /**
     * 查找值在队列中的位置（从后往前搜索）
     */
    public int lrevindex(String key, String value) {
        int index = -1;
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        Assert.isTrue(StringUtils.isNotEmpty(value), "Redis Value is not null");
        List<String> list = stringRedisTemplate.boundListOps(key).range(0, -1);
        for (int i = list.size() - 1; i >= 0; i--) {
            if (list.get(i).equals(value)) {
                index = list.size() - 1 - i;
                break;
            }
        }
        return index;
    }

    /**
     * 查找元素在队列中的位置（从前往后搜索）
     */
    public int lindex(String key, String value) {
        int index = -1;
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        Assert.isTrue(StringUtils.isNotEmpty(value), "Redis Value is not null");
        List<String> list = stringRedisTemplate.boundListOps(key).range(0, -1);
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals(value)) {
                index = i;
                break;
            }
        }
        return index;
    }

    /**
     * 消息订阅（发布消息）
     */
    public void sendMessage(String topic, Object object) {
        Assert.isTrue(StringUtils.isNotEmpty(topic), "Redis topic is not null");
        String value = JSON.toJSONString(object);
        stringRedisTemplate.convertAndSend(topic, value);
        log.debug("sendMessage. [OK] topic={}", topic);
    }

    /**
     * 返回匹配的Key值列表
     */
    public Set<String> keys(String key) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        return stringRedisTemplate.keys(key + "*");
    }

    /**
     * [Set]设置一个值
     */
    public long sadd(String key, String... value) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        long len = stringRedisTemplate.boundSetOps(key).add(value);
        log.debug("sadd. [OK] key={}, object={}", key, value);
        return len;
    }

    /**
     * [Set]设置一个值
     */
    public void sadd(String key, String value, Long time, TimeUnit timeUnit) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                stringRedisTemplate.boundSetOps(key).add(value);
                stringRedisTemplate.boundSetOps(key).expire(time, timeUnit);
                return operations.exec();
            }
        });
        log.debug("sadd. [OK] key={}, value={}", key, value);
    }

    /**
     * [Set]移除一个值
     */
    public long sremove(String key, String... value) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        long len = stringRedisTemplate.boundSetOps(key).remove(value);
        log.debug("sremove. [OK] key={}, object={}", key, value);
        return len;
    }

    /**
     * [Set]取所有元素值
     */
    public Set<String> srange(String key) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        Set<String> set = stringRedisTemplate.boundSetOps(key).members();
        log.debug("srange. [OK] key={}", key);
        return set;
    }

    /**
     * 获取Set所有值
     */
    public <T> Set<T> srange(String key, Class<T> classz) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        Set<String> set = stringRedisTemplate.boundSetOps(key).members();
        Set<T> sets = new ConcurrentHashSet();
        set.forEach(s -> {
            sets.add(JsonUtil.toBean(s, classz));
        });
        log.debug("srange. [OK] key={}", key);
        return sets;
    }

    /**
     * 统计set的size
     */
    public long scount(String key) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        Long size = stringRedisTemplate.boundSetOps(key).size();
        log.debug("scount. [OK] key={}", key);
        return size;
    }

    /**
     * [Hash] 设置值
     */
    public void hput(String key, String field, String value) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        stringRedisTemplate.boundHashOps(key).put(field, value);
        log.debug("hpush. [OK] key={}, field={}, value={}", key, field, value);
    }

    /**
     * [Hash] 获取值
     */
    public String hget(String key, String field) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        Object value = stringRedisTemplate.boundHashOps(key).get(field);
        log.debug("hpush. [OK] key={}, field={}, value={}", key, field, value);
        return value == null ? null : value.toString();
    }

    /**
     * [Hash] 设置值
     */
    public void hput(String key, String field, String value, Long time, TimeUnit timeUnit) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                stringRedisTemplate.boundHashOps(key).put(field, value);
                stringRedisTemplate.boundHashOps(key).expire(time, timeUnit);
                return operations.exec();
            }
        });
        log.debug("hpush. [OK] key={}, field={}, value={}", key, field, value);
    }

    /**
     * [Hash] 移除值
     */
    public void hremove(String key, String field) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        stringRedisTemplate.boundHashOps(key).delete(field);
        log.debug("hremove. [OK] key={}, field={}", key, field);
    }

    /**
     * 队列中存储固定长度的值
     */
    public void lpushLimit(String key, String value, Long length, Long time, TimeUnit timeUnit) {
        Assert.isTrue(StringUtils.isNotEmpty(key), "Redis key is not null");
        stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                stringRedisTemplate.boundListOps(key).leftPush(value);
                stringRedisTemplate.boundListOps(key).trim(0L, length - 1);
                stringRedisTemplate.boundListOps(key).expire(time, timeUnit);
                return operations.exec();
            }
        });
        log.debug("lpushLimit. [OK] key={}, value={}, length={}", key, value, length);
    }

}
